// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

#include "Private.h"
#include "Globals.h"
#include "SampleIME.h"
#include "CompositionProcessorEngine.h"

//+---------------------------------------------------------------------------
//
// ITfCompositionSink::OnCompositionTerminated
//
// Callback for ITfCompositionSink.  The system calls this method whenever
// someone other than this service ends a composition.
//----------------------------------------------------------------------------

STDAPI CSampleIME::OnCompositionTerminated(TfEditCookie ecWrite, _In_ ITfComposition* pComposition) {
	// Clear dummy composition
	_RemoveDummyCompositionForComposing(ecWrite, pComposition);

	// Clear display attribute and end composition, _EndComposition will release composition for us
	ITfContext* pContext = _pContext;
	if (pContext) {
		pContext->AddRef();
	}

	_EndComposition(_pContext);

	_DeleteCandidateList(FALSE, pContext);

	if (pContext) {
		pContext->Release();
		pContext = nullptr;
	}

	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _IsComposing
//
//----------------------------------------------------------------------------

BOOL CSampleIME::_IsComposing() {
	return _pComposition != nullptr;
}

//+---------------------------------------------------------------------------
//
// _SetComposition
//
//----------------------------------------------------------------------------

void CSampleIME::_SetComposition(_In_ ITfComposition* pComposition) {
	_pComposition = pComposition;
}

//+---------------------------------------------------------------------------
//
// _AddComposingAndChar
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_AddComposingAndChar(TfEditCookie ec, _In_ ITfContext* pContext, _In_ CStringRange* pstrAddString) {
	HRESULT hr = S_OK;

	ULONG fetched = 0;
	TF_SELECTION tfSelection;

	if (pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched) != S_OK || fetched == 0)
		return S_FALSE;

	//
	// make range start to selection
	//
	ITfRange* pAheadSelection = nullptr;
	hr = pContext->GetStart(ec, &pAheadSelection);
	if (SUCCEEDED(hr)) {
		hr = pAheadSelection->ShiftEndToRange(ec, tfSelection.range, TF_ANCHOR_START);
		if (SUCCEEDED(hr)) {
			ITfRange* pRange = nullptr;
			BOOL exist_composing = _FindComposingRange(ec, pContext, pAheadSelection, &pRange);

			_SetInputString(ec, pContext, pRange, pstrAddString, exist_composing);

			if (pRange) {
				pRange->Release();
			}
		}
	}

	tfSelection.range->Release();

	if (pAheadSelection) {
		pAheadSelection->Release();
	}

	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _AddCharAndFinalize
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_AddCharAndFinalize(TfEditCookie ec, _In_ ITfContext* pContext, _In_ CStringRange* pstrAddString) {
	HRESULT hr = E_FAIL;

	ULONG fetched = 0;
	TF_SELECTION tfSelection;

	if ((hr = pContext->GetSelection(ec, TF_DEFAULT_SELECTION, 1, &tfSelection, &fetched)) != S_OK || fetched != 1)
		return hr;

	// we use SetText here instead of InsertTextAtSelection because we've already started a composition
	// we don't want to the app to adjust the insertion point inside our composition
	hr = tfSelection.range->SetText(ec, 0, pstrAddString->Get(), (LONG)pstrAddString->GetLength());
	if (hr == S_OK) {
		// update the selection, we'll make it an insertion point just past
		// the inserted text.
		tfSelection.range->Collapse(ec, TF_ANCHOR_END);
		pContext->SetSelection(ec, 1, &tfSelection);
	}

	tfSelection.range->Release();

	return hr;
}

//+---------------------------------------------------------------------------
//
// _FindComposingRange
//
//----------------------------------------------------------------------------

BOOL CSampleIME::_FindComposingRange(TfEditCookie ec, _In_ ITfContext* pContext, _In_ ITfRange* pSelection, _Outptr_result_maybenull_ ITfRange** ppRange) {
	if (ppRange == nullptr) {
		return FALSE;
	}

	*ppRange = nullptr;

	// find GUID_PROP_COMPOSING
	ITfProperty* pPropComp = nullptr;
	IEnumTfRanges* enumComp = nullptr;

	HRESULT hr = pContext->GetProperty(GUID_PROP_COMPOSING, &pPropComp);
	if (FAILED(hr) || pPropComp == nullptr) {
		return FALSE;
	}

	hr = pPropComp->EnumRanges(ec, &enumComp, pSelection);
	if (FAILED(hr) || enumComp == nullptr) {
		pPropComp->Release();
		return FALSE;
	}

	BOOL isCompExist = FALSE;
	VARIANT var;
	ULONG  fetched = 0;

	while (enumComp->Next(1, ppRange, &fetched) == S_OK && fetched == 1) {
		hr = pPropComp->GetValue(ec, *ppRange, &var);
		if (hr == S_OK) {
			if (var.vt == VT_I4 && var.lVal != 0) {
				isCompExist = TRUE;
				break;
			}
		}
		(*ppRange)->Release();
		*ppRange = nullptr;
	}

	pPropComp->Release();
	enumComp->Release();

	return isCompExist;
}

//+---------------------------------------------------------------------------
//
// _SetInputString
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_SetInputString(TfEditCookie ec, _In_ ITfContext* pContext, _Out_opt_ ITfRange* pRange, _In_ CStringRange* pstrAddString, BOOL exist_composing) {
	ITfRange* pRangeInsert = nullptr;
	if (!exist_composing) {
		_InsertAtSelection(ec, pContext, pstrAddString, &pRangeInsert);
		if (pRangeInsert == nullptr) {
			return S_OK;
		}
		pRange = pRangeInsert;
	}
	if (pRange != nullptr) {
		pRange->SetText(ec, 0, pstrAddString->Get(), (LONG)pstrAddString->GetLength());
	}

	_SetCompositionLanguage(ec, pContext);

	_SetCompositionDisplayAttributes(ec, pContext, _gaDisplayAttributeInput);

	// update the selection, we'll make it an insertion point just past
	// the inserted text.
	ITfRange* pSelection = nullptr;
	TF_SELECTION sel;

	if ((pRange != nullptr) && (pRange->Clone(&pSelection) == S_OK)) {
		pSelection->Collapse(ec, TF_ANCHOR_END);

		sel.range = pSelection;
		sel.style.ase = TF_AE_NONE;
		sel.style.fInterimChar = FALSE;
		pContext->SetSelection(ec, 1, &sel);
		pSelection->Release();
	}

	if (pRangeInsert) {
		pRangeInsert->Release();
	}


	return S_OK;
}

//+---------------------------------------------------------------------------
//
// _InsertAtSelection
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_InsertAtSelection(TfEditCookie ec, _In_ ITfContext* pContext, _In_ CStringRange* pstrAddString, _Outptr_ ITfRange** ppCompRange) {
	ITfRange* rangeInsert = nullptr;
	ITfInsertAtSelection* pias = nullptr;
	HRESULT hr = S_OK;

	if (ppCompRange == nullptr) {
		hr = E_INVALIDARG;
		goto Exit;
	}

	*ppCompRange = nullptr;

	hr = pContext->QueryInterface(IID_ITfInsertAtSelection, (void**)&pias);
	if (FAILED(hr)) {
		goto Exit;
	}

	hr = pias->InsertTextAtSelection(ec, TF_IAS_QUERYONLY, pstrAddString->Get(), (LONG)pstrAddString->GetLength(), &rangeInsert);

	if (FAILED(hr) || rangeInsert == nullptr) {
		rangeInsert = nullptr;
		pias->Release();
		goto Exit;
	}

	*ppCompRange = rangeInsert;
	pias->Release();
	hr = S_OK;

Exit:
	return hr;
}

//+---------------------------------------------------------------------------
//
// _RemoveDummyCompositionForComposing
//
//----------------------------------------------------------------------------

HRESULT CSampleIME::_RemoveDummyCompositionForComposing(TfEditCookie ec, _In_ ITfComposition* pComposition) {
	HRESULT hr = S_OK;

	ITfRange* pRange = nullptr;

	if (pComposition) {
		hr = pComposition->GetRange(&pRange);
		if (SUCCEEDED(hr)) {
			pRange->SetText(ec, 0, nullptr, 0);
			pRange->Release();
		}
	}

	return hr;
}

//+---------------------------------------------------------------------------
//
// _SetCompositionLanguage
//
//----------------------------------------------------------------------------

BOOL CSampleIME::_SetCompositionLanguage(TfEditCookie ec, _In_ ITfContext* pContext) {
	HRESULT hr = S_OK;
	BOOL ret = TRUE;

	CCompositionProcessorEngine* pCompositionProcessorEngine = nullptr;
	pCompositionProcessorEngine = _pCompositionProcessorEngine;

	LANGID langidProfile = 0;
	pCompositionProcessorEngine->GetLanguageProfile(&langidProfile);

	ITfRange* pRangeComposition = nullptr;
	ITfProperty* pLanguageProperty = nullptr;

	// we need a range and the context it lives in
	hr = _pComposition->GetRange(&pRangeComposition);
	if (FAILED(hr)) {
		ret = FALSE;
		goto Exit;
	}

	// get our the language property
	hr = pContext->GetProperty(GUID_PROP_LANGID, &pLanguageProperty);
	if (FAILED(hr)) {
		ret = FALSE;
		goto Exit;
	}

	VARIANT var;
	var.vt = VT_I4;   // we're going to set DWORD
	var.lVal = langidProfile;

	hr = pLanguageProperty->SetValue(ec, pRangeComposition, &var);
	if (FAILED(hr)) {
		ret = FALSE;
		goto Exit;
	}

	pLanguageProperty->Release();
	pRangeComposition->Release();

Exit:
	return ret;
}
